#ifndef XG_HTTPSTREAM_CPP
#define XG_HTTPSTREAM_CPP
//////////////////////////////////////////////////////////
#include "../HttpStream.h"
#include "../HttpRequest.h"
#include "../HttpResponse.h"

HttpStream::~HttpStream()
{
	if (sock)
	{
		HttpFrameData* frame = HttpSettingItem::GetGoawayFrame();
		response(frame, frame->getFrameSize());
	}
}
const HttpFrame& HttpStream::get(const HttpFrame& item) const
{
	static HttpHeadVector vec;
	static HttpFrame frame(vec);
	const auto it = fs.find(item);

	if (it == fs.end()) return frame;

	return *it;
}
void HttpStream::run()
{
	SOCKET conn = sock->getHandle();

	if (errcode == XG_TIMEOUT)
	{
		errcode = 0;

		if (ServerSocketAttach(conn, addr.c_str()))
		{
			LogTrace(eDBG, "connect[%s] attach", addr.c_str());
		}
		else
		{
			HttpServer::ReleaseConnect(conn, addr.c_str());
		}
	}
	else
	{
		LogTrace(eERR, "connect[%s] process failed[%d]", addr.c_str(), errcode < 0 ? errcode : XG_ERROR);

		HttpServer::ReleaseConnect(conn, addr.c_str());
	}
}
int HttpStream::read()
{
	int len = 0;
	u_int32 sz = 0;
	u_int32 id = 0;
	u_int32 ofs = 0;
	HttpFrameData* hdr = (HttpFrameData*)(buffer);

	if (readed < HTTP2_HEAD_SIZE || readed < hdr->getFrameSize())
	{
		len = sock->read(buffer + readed, sizeof(buffer) - readed, false);

		if (len < 0) return len;
		if (len == 0) return XG_TIMEOUT;
		if ((readed += len) < HTTP2_HEAD_SIZE) return readed;
	}

	sz = hdr->getFrameSize();

	while (sz <= readed)
	{
		if (sz > HTTP2_HEAD_SIZE + HTTP2_FRAME_MAXSIZE) return XG_DATAERR;

		if ((id = hdr->getId()) == 0)
		{
			if ((len = update(hdr, sz)) < 0) return len;
		}
		else
		{
			HttpFrame item(*vec);

			if ((len = item.init(hdr, sz)) < 0) return len;

			if (item.isHeadFrame())
			{
				if (item.isEndStream())
				{
					if ((len = process(item)) < 0) return len;
				}
				else
				{
					fs.insert(item);
				}
			}
			else if (item.isDataFrame() || item.isAddFrame())
			{
				const HttpFrame& frame = get(item);
			
				if (frame.getId() > 0)
				{
					if ((len = frame.add(item)) < 0) return len;
			
					if (frame.isEndStream())
					{
						if ((len = process(frame)) < 0) return len;
			
						fs.erase(frame);
					}
				}
			}
			else if (item.isRstFrame())
			{
				auto it = filelist.begin();
				
				while (it != filelist.end())
				{
					if (id == it->id)
					{
						it = filelist.erase(it);
					}
					else
					{
						++it;
					}
				}
				
				fs.erase(item);
			}
		}

		ofs += sz;
		readed -= sz;

		if (readed < HTTP2_HEAD_SIZE) break;
		
		hdr = (HttpFrameData*)(buffer + ofs);
		sz = hdr->getFrameSize();
	}

	if (ofs > 0 && readed > 0) memmove(buffer, buffer + ofs, readed);

	return readed;
}
int HttpStream::process(const HttpFrame& frame)
{
	HttpRequest request;
	HttpResponse response;

	if (request.init(frame) < 0) return XG_DATAERR;

	int val = XG_SYSERR;
	u_int32 id = frame.getId();
	const string& url = request.getPath();

	if (response.init(&request, sock, addr))
	{
		val = response.forward(url, addr, [&](const void* msg, int len, const string& addr){
			sp<IFile> file;
			SmartBuffer data;

			if (len < 0)
			{
				sp<XFile> tmp = newsp<XFile>();

				if (tmp->open((char*)(msg), eREAD)) len = tmp->size();

				if (len < 0) return XG_NOTFOUND;
				
				if (len < HTTP2_FRAME_MAXSIZE)
				{
					if (tmp->read(data.malloc(len), len) < len) return XG_IOERR;
				}
				else
				{
					response.node["Content-Length"] = stdx::str(len);
				
					file = tmp;
				}
			}
			else
			{
				if (len > 0)
				{
					memcpy(data.malloc(len), msg, len);

					if (len > HTTP2_FRAME_MAXSIZE)
					{
						sp<MemFile> tmp = newsp<MemFile>();

						response.node["Content-Length"] = stdx::str(len);
						tmp->open(data);
						data.free();
						file = tmp;
					}
				}
			}

			response.node[":status"] = stdx::str(response.getStatus());

			data = HttpFrame::Encode(id, response.node, data, vec[1]);

			if (data.isNull()) return XG_SYSERR;

			if ((val = this->response(data.str(), data.size())) < 0) return val;

			if (file) filelist.push_back(FileItem(file, len, id));
			
			return len;
		});

		if (val == XG_NOTFOUND || val == XG_AUTHFAIL)
		{
			SmartBuffer data;
	
			if (val == XG_NOTFOUND)
			{
				LogTrace(eIMP, "request[%s][%s] resource not found", addr.c_str(), url.c_str());

				data = response.getNotFoundContent(false);
			}
			else
			{
				LogTrace(eIMP, "request[%s][%s] resource forbidden", addr.c_str(), url.c_str());

				data = response.getRefusedContent(false);
			}

			if (data.isNull())
			{
				val = XG_SYSERR;
			}
			else
			{
				if (val == XG_NOTFOUND)
				{
					response.setStatus(404);
				}
				else
				{
					response.setStatus(403);
				}

				response.node[":status"] = stdx::str(response.getStatus());

				data = HttpFrame::Encode(id, response.node, data, vec[1]);

				if (data.isNull())
				{
					val = XG_SYSERR;
				}
				else
				{
					val = this->response(data.str(), data.size());
				}
			}
		}
	}

	if (val >= 0)
	{
		LogTrace(eINF, "request[%s][%s] process success[%d]", addr.c_str(), url.c_str(), val);
		
		if (maxid < id) maxid = id;
		
		++inited;
	}
	else
	{
		LogTrace(eERR, "request[%s][%s] process failed[%d]", addr.c_str(), url.c_str(), val);
	}

	return val;
}
int HttpStream::response(const void* msg, int len)
{
	int num = 0;

	if (rsplen == 0)
	{
		if (rspdata.size() > HTTP_SENDBUF_MAXSIZE) rspdata.free();

		if ((writed = sock->write(msg, len, false)) < 0) return writed;

		if (writed > SOCKET_TIMEOUT_LIMITSIZE) utime = time(NULL);

		msg = (char*)(msg) + writed;
		len -= writed;
		num = writed;
		writed = 0;

		if (len > rspdata.size()) rspdata.malloc(len);
	}
	else
	{
		if (rsplen + len > HTTP_REQDATA_MAXSIZE) return XG_NETDELAY;

		if (writed > 0)
		{
			memmove(rspdata.str(), rspdata.str() + writed, rsplen);
			writed = 0;
		}

		if (rsplen + len > rspdata.size()) rspdata.truncate(rsplen + len);
	}

	if (len > 0)
	{
		memcpy(rspdata.str() + rsplen, msg, len);
		rsplen += len;
	}

	return rsplen ? rsplen : num;
}
int HttpStream::update(HttpFrameData* hdr, int len)
{
	if (hdr->type == HTTP2_GOAWAY_FRAME)
	{
		return XG_NETCLOSE;
	}
	else if (hdr->type == HTTP2_PING_FRAME)
	{
		if ((hdr->flag & HTTP2_ACK_FLAG) == 0)
		{
			hdr->flag = HTTP2_ACK_FLAG;

			if ((len = response(hdr, hdr->getFrameSize())) < 0) return len;
		}
	}
	else if (hdr->type == HTTP2_SETTINGS_FRAME)
	{
		if ((hdr->flag & HTTP2_ACK_FLAG) == 0)
		{
			HttpFrameData* frame = HttpSettingItem::GetAckFrame();

			if ((len = response(frame, frame->getFrameSize())) < 0) return len;
		}
	}

	return len;
}
bool HttpStream::send()
{
	if (rsplen > 0)
	{
		int len = sock->write(rspdata.str() + writed, rsplen, false);

		if (len > 0)
		{
			writed += len;
			rsplen -= len;

			if (len > SOCKET_TIMEOUT_LIMITSIZE)
			{
				utime = time(NULL);
			}
			else
			{
				if (utime + timeout < time(NULL))
				{
					errcode = XG_NETDELAY;

					return true;
				}
			}

			if (rsplen == 0 && rspdata.size() > HTTP_SENDBUF_MAXSIZE) rspdata.free();
		}
		else if (len == 0)
		{
			if (utime + timeout < time(NULL))
			{
				errcode = XG_NETDELAY;

				return true;
			}
		}
		else
		{
			errcode = len;

			return true;
		}
	}
	else
	{
		int num = filelist.size();

		if (num > 0)
		{
			FileItem& item = filelist.front();
			char buffer[HTTP2_HEAD_SIZE + SOCKECT_FRAMESIZE];
			int len = item.file->read(buffer + HTTP2_HEAD_SIZE, SOCKECT_FRAMESIZE);

			HttpFrameData* hdr = (HttpFrameData*)(buffer);
		
			if (len <= 0 || (item.readed += len) > item.filelen)
			{
				filelist.pop_front();
				errcode = XG_IOERR;
		
				return true;
			}

			hdr->flag = item.readed == item.filelen ? HTTP2_END_STREAM : 0;
			hdr->type = HTTP2_DATA_FRAME;
			hdr->setId(item.id);
			hdr->setSize(len);

			if ((len = response(hdr, hdr->getFrameSize())) < 0)
			{
				filelist.pop_front();
				errcode = len;
		
				return true;
			}

			if (item.readed == item.filelen)
			{
				filelist.pop_front();
			}
			else
			{
				if (num > 1)
				{
					filelist.push_back(item);
					filelist.pop_front();
				}
			}
		}
	}

	return false;
}
bool HttpStream::runnable()
{
	++index;

	if (inited > 0)
	{
		if (rsplen > 0 || filelist.size() > 0)
		{
			if (index % 8) return send();
		}

		int len = read();

		if (len < 0)
		{
			if (len == XG_TIMEOUT)
			{
				if (rsplen > 0 || filelist.size() > 0) return send();

				if (fs.size() > 0)
				{
					if (utime + timeout < time(NULL)) return false;
				}
			}

			errcode = len;

			return true;
		}

		if (len > SOCKET_TIMEOUT_LIMITSIZE) utime = time(NULL);

		if (rsplen > 0 || filelist.size() > 0) return send();
	}
	else
	{
		int len = sock->read(buffer + readed, sizeof(buffer) - readed, false);

		utime = time(NULL);

		if (len > 0)
		{
			static char tag[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";

			if ((readed += len) + 1 >= sizeof(tag))
			{
				char* end = tag + sizeof(tag) - 1;
				char* str = std::search(buffer, buffer + readed, tag, end);

				if (str < buffer + readed)
				{
					str += sizeof(tag) - 1;
					len = str - buffer;
					inited = 1;

					if ((readed -= len) > 0) memmove(buffer, str, readed);

					static int hdrsz = 0;
					static char head[64] = {0};

					if (hdrsz == 0)
					{
						HttpFrameData* frame = HttpSettingItem::GetCommonFrame();

						len = frame->getFrameSize();
						memcpy(head, frame, len);

						frame = (HttpFrameData*)(head + len);

						frame->setSize(4);
						frame->type = HTTP2_WINDOWUPDATE_FRAME;
						*(u_int*)(frame->data) = htonl(HTTP2_WINDOW_MAXSIZE);

						hdrsz = len + frame->getFrameSize();
					}

					if (response(head, hdrsz) < 0)
					{
						errcode = XG_SYSERR;

						return true;
					}
				}
				else
				{
					char* str = std::search(buffer, buffer + readed, HTTP_REQHDR_ENDSTR, HTTP_REQHDR_ENDSTR + HTTP_REQHDR_ENDSTR_LEN);

					if (str < buffer + readed)
					{
						static char msg[] = "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n";

						str += HTTP_REQHDR_ENDSTR_LEN;
						len = str - buffer;

						if ((readed -= len) > 0) memmove(buffer, str, readed);

						if (response(msg, sizeof(msg) - 1) < 0)
						{
							errcode = XG_SYSERR;

							return true;
						}
					}
				}
			}
			else if (len == 0)
			{
				errcode = XG_TIMEOUT;

				return true;
			}
			else
			{
				errcode = len;

				return true;
			}
		}
	}

	return false;
}

//////////////////////////////////////////////////////////
#endif